package com.snail.wms.stock.business.stock.service.impl;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.map.MapUtil;
import com.snail.common.core.exception.ServiceException;
import com.snail.common.core.utils.DateUtils;
import com.snail.common.core.utils.SpringUtils;
import com.snail.common.core.utils.StringUtils;
import com.snail.common.core.utils.ValidationUtils;
import com.snail.common.core.web.page.PageResult;
import com.snail.common.lock.Lock;
import com.snail.wms.stock.business.stock.constants.WmsStockConstants;
import com.snail.wms.stock.business.stock.domain.*;
import com.snail.wms.stock.business.stock.dto.*;
import com.snail.wms.stock.business.stock.mq.StockCallDto;
import com.snail.wms.stock.business.stock.mq.StockCallbackDto;
import com.snail.wms.stock.business.stock.query.*;
import com.snail.wms.stock.business.stock.service.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.validation.Validator;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.*;

/**
 * @Description: 库存管理业务实现
 * @Author: snail
 * @CreateDate: 2024/3/18 16:49
 * @Version: V1.0
 */
@Service
public class WmsStockServiceImpl implements IWmsStockService {

    @Autowired
    private IWmsStockAmountService wmsStockAmountService;
    @Autowired
    private IWmsStockWarehouseService wmsStockWarehouseService;
    @Autowired
    private IWmsStockDimensionsService wmsStockDimensionsService;
    @Autowired
    private IWmsStockAttributesService wmsStockAttributesService;
    @Autowired
    private IWmsStockOperateDetailService wmsStockOperateDetailService;
    @Autowired
    private Lock lock;

    @Override
    @Transactional(rollbackFor = Exception.class)
    public StockCallbackDto operateInStock(StockCallDto callDto) {
        StockCallbackDto callbackDto = new StockCallbackDto();
        List<Map<String, String>> errorList = new ArrayList<>();
        //1、验证相关信息（必填等等）
        String error = checkData(callDto, errorList);
        if (StringUtils.isNotEmpty(error) || CollUtil.isNotEmpty(errorList)) {
            callbackDto.setMsg(error);
            callbackDto.setErrorList(errorList);
            return callbackDto;
        }
        //分布式锁保证数据的可靠性
        String lockKey = WmsStockConstants.LOCK_KEY + callDto.getWarehouseId();
        if (!lock.getLock(lockKey)) {
            throw new ServiceException("获取分布式锁失败！");
        }
        try {
            List<StockResultDto> resultDtoList = new ArrayList<>();
            //2、保存仓库信息
            for (StockInputDto stockInputDto : callDto.getInputDtoList()) {
                StockResultDto resultDto = new StockResultDto();
                WmsStockAmount stockAmount = new WmsStockAmount();
                //总库存量
                stockAmount.setTotalAmount(stockInputDto.getAmount());
                stockAmount.setPreemptionAmount(BigDecimal.ZERO);
                stockAmount.setFreezeAmount(BigDecimal.ZERO);
                stockAmount.setOutAmount(BigDecimal.ZERO);
                //校验并保持仓库信息
                WmsStockWarehouse wmsStockWarehouse = dealStockWarehouse(stockInputDto.getStockWarehouse());
                stockAmount.setStockWarehouseId(wmsStockWarehouse.getStockWarehouseId());
                resultDto.setStockWarehouseId(wmsStockWarehouse.getStockWarehouseId());
                //校验并保持库位维度信息
                WmsStockDimensions wmsStockDimensions = dealStockDimensions(stockInputDto.getStockDimensions());
                stockAmount.setStockDimensionId(wmsStockDimensions.getStockDimensionId());
                resultDto.setStockDimensionId(wmsStockDimensions.getStockDimensionId());
                //保持并校验库存属性信息
                WmsStockAttributes wmsStockAttributes = dealStockAttribute(stockInputDto.getStockAttributeId(), wmsStockWarehouse, stockInputDto.getStockAttributes());
                stockAmount.setStockAttributeId(wmsStockAttributes.getStockAttributeId());

                wmsStockAmountService.save(stockAmount);

                resultDto.setStockAmountId(stockAmount.getId());
                resultDto.setStockAttributeId(wmsStockAttributes.getStockAttributeId());
                resultDto.setStockBatch(wmsStockAttributes.getStockBatch());
                resultDto.setBillDetailId(wmsStockAttributes.getInDetailId());
                resultDto.setType(callDto.getType());
                resultDto.setBillId(callDto.getBillId());
                resultDto.setBillCode(wmsStockAttributes.getInCode());
                resultDto.setAmount(stockInputDto.getAmount());
                resultDtoList.add(resultDto);
            }

            callbackDto.setResult(Boolean.TRUE);
            callbackDto.setResultDtoList(resultDtoList);
        } catch (Exception e) {
            callbackDto.setMsg("生成库存异常:" + e.getMessage());
        } finally {
            lock.unLock(lockKey);
        }
        return callbackDto;
    }

    /**
     * 预占库存
     *
     * @param callDto     输入信息
     * @param callbackDto 输出信息
     * @return 输出信息
     */
    private void preemptionStock(StockCallDto callDto, StockCallbackDto callbackDto) {
        //失败信息
        List<Map<String, String>> errorList = new ArrayList<>();
        //成功信息集合
        List<StockResultDto> resultDtoList = new ArrayList<>();
        try {
            //2.操作库存数据
            Map<String, StockAmountDto> amountMap = operationStock(callDto, resultDtoList, errorList);
            //如果没有错误信息,更新数据
            if (CollUtil.isEmpty(errorList)) {
                wmsStockAmountService.preemptionStockAmount(new ArrayList<>(amountMap.values()));
                //保存库存操作流水
                wmsStockOperateDetailService.createStockOperateDetail(resultDtoList);
                callbackDto.setResult(Boolean.TRUE);
                //成功信息
                callbackDto.setResultDtoList(resultDtoList);
            } else {
                //失败信息
                callbackDto.setErrorList(errorList);
            }
        } catch (Exception e) {
            callbackDto.setMsg("预占库存异常：" + e.getMessage());
        }
    }

    /**
     * 操作库存数据
     *
     * @param callDto       输入
     * @param resultDtoList 返回结果
     * @param errorList     错误信息
     */
    private Map<String, StockAmountDto> operationStock(StockCallDto callDto, List<StockResultDto> resultDtoList, List<Map<String, String>> errorList) {
        //数据更新的map
        Map<String, StockAmountDto> amountMap = new HashMap<>();
        //遍历操作库存的数据
        for (StockOutputDto stockOutputDto : callDto.getOutputDtoList()) {
            //错误信息map
            Map<String, String> errorMap = new HashMap<>();
            //单据明细id
            String billDetailId = stockOutputDto.getBillDetailId();
            //1.查找库存维度
            WmsStockDimensionsQuery dimensionsQuery = new WmsStockDimensionsQuery();
            dimensionsQuery.setSkuId(stockOutputDto.getSkuId());
            List<WmsStockDimensions> dimensions = wmsStockDimensionsService.selectWmsStockDimensionsList(dimensionsQuery);
            if (CollUtil.isEmpty(dimensions)) {
                errorMap.put(billDetailId, "库存维度数据不存在!");
                errorList.add(errorMap);
                break;
            }
            WmsStockDimensions wmsStockDimensions = dimensions.get(0);

            //2.查找库存仓库
            WmsStockWarehouseQuery warehouseQuery = new WmsStockWarehouseQuery();
            warehouseQuery.setWarehouseId(stockOutputDto.getWarehouseId());
            warehouseQuery.setLocationId(stockOutputDto.getLocationId());
            List<WmsStockWarehouse> stockWarehouses = wmsStockWarehouseService.selectWmsStockWarehouseList(warehouseQuery);
            if (CollUtil.isEmpty(stockWarehouses)) {
                errorMap.put(billDetailId, "库存仓库数据不存在!");
                errorList.add(errorMap);
                break;
            }
            WmsStockWarehouse wmsStockWarehouse = stockWarehouses.get(0);

            //3.查找库存属性,如果传了批次就查询
            WmsStockAttributes wmsStockAttributes = null;
            if (StringUtils.isNotEmpty(stockOutputDto.getStockBatch())) {
                WmsStockAttributesQuery attributesQuery = new WmsStockAttributesQuery();
                attributesQuery.setStockBatch(stockOutputDto.getStockBatch());
                List<WmsStockAttributes> stockAttributes = wmsStockAttributesService.selectWmsStockAttributesList(attributesQuery);
                if (CollUtil.isEmpty(stockAttributes)) {
                    errorMap.put(billDetailId, "库存批次不存在!");
                    errorList.add(errorMap);
                    break;
                }
                wmsStockAttributes = stockAttributes.get(0);
            }
            //4.查询库存数量
            List<StockAmountDto> stockAmountDtoList = wmsStockAmountService.selectStockAmount(wmsStockWarehouse.getStockWarehouseId(), wmsStockDimensions.getStockDimensionId(), wmsStockAttributes != null ? wmsStockAttributes.getStockAttributeId() : "");
            if (CollUtil.isEmpty(stockAmountDtoList)) {
                errorMap.put(billDetailId, "库存不足!");
                errorList.add(errorMap);
                break;
            }
            //5.遍历库存数量，组装数据
            buildStockAmountData(stockOutputDto, stockAmountDtoList, amountMap, resultDtoList, errorMap, callDto.getType());
            if (!errorMap.isEmpty()) {
                errorList.add(errorMap);
            }
        }
        return amountMap;
    }

    /**
     * 封装库存数量相关数据
     *
     * @param stockOutputDto     出库数据
     * @param stockAmountDtoList 库存数量
     * @param amountMap          库存属性更新数据
     * @param resultDtoList      返回结果数据
     * @param errorMap           错误信息
     * @param type               操作类型：P.预占、F.冻结、O.实占
     */
    private void buildStockAmountData(StockOutputDto stockOutputDto,
                                      List<StockAmountDto> stockAmountDtoList,
                                      Map<String, StockAmountDto> amountMap,
                                      List<StockResultDto> resultDtoList,
                                      Map<String, String> errorMap,
                                      String type) {
        //剩余需要出库的数量
        BigDecimal nextAmount = stockOutputDto.getAmount();
        for (StockAmountDto amountDto : stockAmountDtoList) {
            StockResultDto resultDto = new StockResultDto();
            resultDto.setBillId(stockOutputDto.getBillId());
            resultDto.setBillCode(stockOutputDto.getBillCode());
            resultDto.setType(type);
            resultDto.setBillDetailId(stockOutputDto.getBillDetailId());
            resultDto.setStockAmountId(amountDto.getStockAmountId());
            resultDto.setStockWarehouseId(amountDto.getStockWarehouseId());
            resultDto.setStockDimensionId(amountDto.getStockDimensionId());
            resultDto.setStockAttributeId(amountDto.getStockAttributeId());
            resultDto.setStockBatch(amountDto.getStockBatch());
            //获取map中对应库存数量，如果没有就默认使用当前的
            StockAmountDto stockAmountDto = MapUtil.get(amountMap, amountDto.getStockAmountId(), StockAmountDto.class, amountDto);
            //可用数量=总数量-预占数量-冻结数量-出库数量
            BigDecimal canUseAmount = stockAmountDto.getTotalAmount().subtract(stockAmountDto.getPreemptionAmount()).subtract(stockAmountDto.getFreezeAmount()).subtract(stockAmountDto.getOutAmount());
            //如果可用数量大于等于需要出库的数量（map中的数量+本次要出库数量） 跳出循环
            if (canUseAmount.compareTo(nextAmount) > -1) {
                switch (type) {
                    case WmsStockConstants.OPERATE_TYPE_P:
                        resultDto.setAmount(nextAmount);
                        stockAmountDto.setPreemptionAmount(stockAmountDto.getPreemptionAmount().add(nextAmount));
                        break;
                    case WmsStockConstants.OPERATE_TYPE_F:
                        resultDto.setAmount(nextAmount);
                        stockAmountDto.setFreezeAmount(stockAmountDto.getPreemptionAmount().add(nextAmount));
                        break;
                    case WmsStockConstants.OPERATE_TYPE_O:
                        resultDto.setAmount(nextAmount);
                        stockAmountDto.setOutAmount(stockAmountDto.getOutAmount().add(nextAmount));
                        break;
                }
                nextAmount = nextAmount.subtract(nextAmount);
                amountMap.put(amountDto.getStockAmountId(), stockAmountDto);
                //返回结果数据
                resultDtoList.add(resultDto);
                break;
            }
            //可用数量 小于 需要出库的数量
            //更新剩余需要出库的数量
            nextAmount = nextAmount.subtract(canUseAmount);
            resultDto.setAmount(canUseAmount);
            switch (type) {
                case WmsStockConstants.OPERATE_TYPE_P:
                    resultDto.setAmount(nextAmount);
                    stockAmountDto.setPreemptionAmount(stockAmountDto.getPreemptionAmount().add(canUseAmount));
                    break;
                case WmsStockConstants.OPERATE_TYPE_F:
                    resultDto.setAmount(nextAmount);
                    stockAmountDto.setFreezeAmount(stockAmountDto.getFreezeAmount().add(canUseAmount));
                    break;
                case WmsStockConstants.OPERATE_TYPE_O:
                    resultDto.setAmount(nextAmount);
                    stockAmountDto.setOutAmount(stockAmountDto.getOutAmount().add(canUseAmount));
                    break;
            }
            amountMap.put(amountDto.getStockAmountId(), stockAmountDto);
            resultDto.setStockAmountId(amountDto.getStockAmountId());
            //返回结果数据
            resultDtoList.add(resultDto);
        }
        //如果剩余需要出库的数量大于了0，说明库存不足
        if (nextAmount.compareTo(BigDecimal.ZERO) > 0) {
            errorMap.put(stockOutputDto.getBillDetailId(), "库存不足!");
        }
    }

    /**
     * 冻结库存
     *
     * @param callDto     输入信息
     * @param callbackDto 输出信息
     * @return
     */
    private void freezeStock(StockCallDto callDto, StockCallbackDto callbackDto) {
        //错误信息
        List<Map<String, String>> errorList = new ArrayList<>();
        //成功信息集合
        List<StockResultDto> resultDtoList = new ArrayList<>();
        try {
            //2.操作库存数据
            Map<String, StockAmountDto> amountMap = operationStock(callDto, resultDtoList, errorList);
            //如果没有错误信息,更新数据
            if (CollUtil.isEmpty(errorList)) {
                wmsStockAmountService.freezeStockAmount(new ArrayList<>(amountMap.values()));
                //写入库存操作记录
                wmsStockOperateDetailService.createStockOperateDetail(resultDtoList);
                //成功信息
                callbackDto.setResultDtoList(resultDtoList);
                callbackDto.setResult(Boolean.TRUE);
            } else {
                //失败信息
                callbackDto.setErrorList(errorList);
            }
        } catch (Exception e) {
            callbackDto.setMsg("冻结库存异常：" + e.getMessage());
        }
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public StockCallbackDto operateOutStock(StockCallDto callDto) {
        StockCallbackDto callbackDto = new StockCallbackDto();
        List<Map<String, String>> errorList = new ArrayList<>();
        //1、验证相关信息（必填等等）
        String error = checkData(callDto, errorList);
        if (StringUtils.isNotEmpty(error) || CollUtil.isNotEmpty(errorList)) {
            callbackDto.setMsg(error);
            callbackDto.setErrorList(errorList);
            return callbackDto;
        }
        //2、获取分布式锁  保证数据的可靠性
        String lockKey = WmsStockConstants.LOCK_KEY + callDto.getWarehouseId();
        if (!lock.getLock(lockKey)) {
            throw new ServiceException("获取分布式锁失败！");
        }
        switch (callDto.getType()) {
            case WmsStockConstants.OPERATE_TYPE_P:
                this.preemptionStock(callDto, callbackDto);
                break;
            case WmsStockConstants.OPERATE_TYPE_F:
                this.freezeStock(callDto, callbackDto);
                break;
            case WmsStockConstants.OPERATE_TYPE_O:
                this.outStock(callDto, callbackDto);
                break;
            case WmsStockConstants.OPERATE_TYPE_RP:
                this.releasePreemptionStock(callDto, callbackDto);
                break;
            case WmsStockConstants.OPERATE_TYPE_RF:
                this.releaseFreezeStock(callDto, callbackDto);
                break;
            case WmsStockConstants.OPERATE_TYPE_PO:
            case WmsStockConstants.OPERATE_TYPE_FO:
                this.toOutStock(callDto, callbackDto);
                break;
        }
        lock.unLock(lockKey);
        return callbackDto;
    }

    /**
     * 转实占
     *
     * @param callDto     输入信息
     * @param callbackDto 输出信息
     */
    private void toOutStock(StockCallDto callDto, StockCallbackDto callbackDto) {
        //错误信息
        List<Map<String, String>> errorList = new ArrayList<>();
        //成功信息集合
        List<StockResultDto> resultDtoList = new ArrayList<>();
        try {
            //操作库存释放
            Map<String, StockAmountDto> amountMap = operateStockRelease(callDto, errorList, resultDtoList);
            //如果没有错误信息,更新数据
            if (CollUtil.isEmpty(errorList)) {
                //更新出库量
                wmsStockAmountService.outStockAmount(new ArrayList<>(amountMap.values()));
                //释放完成后删除操作明细，这里可用其他方案替代
                wmsStockOperateDetailService.deleteStockOperateDetail(callDto.getBillId());
                //插入操作明细
                wmsStockOperateDetailService.createStockOperateDetail(resultDtoList);
                //成功信息
                callbackDto.setResultDtoList(resultDtoList);
                callbackDto.setResult(Boolean.TRUE);
            } else {
                //失败信息
                callbackDto.setErrorList(errorList);
            }
        } catch (Exception e) {
            callbackDto.setMsg("转实占异常：" + e.getMessage());
        }
    }

    /**
     * 实占库存
     *
     * @param callDto     输入信息
     * @param callbackDto 输出信息
     */
    private void outStock(StockCallDto callDto, StockCallbackDto callbackDto) {
        //错误信息
        List<Map<String, String>> errorList = new ArrayList<>();
        //成功信息集合
        List<StockResultDto> resultDtoList = new ArrayList<>();
        try {
            //直接实占
            Map<String, StockAmountDto> amountMap = operationStock(callDto, resultDtoList, errorList);
            //如果没有错误信息,更新数据
            if (CollUtil.isEmpty(errorList)) {
                //更新库存
                wmsStockAmountService.outStockAmount(new ArrayList<>(amountMap.values()));
                //插入实占库存记录
                wmsStockOperateDetailService.createStockOperateDetail(resultDtoList);
                //成功信息
                callbackDto.setResultDtoList(resultDtoList);
                callbackDto.setResult(Boolean.TRUE);
            } else {
                //失败信息
                callbackDto.setErrorList(errorList);
            }
        } catch (Exception e) {
            callbackDto.setMsg("实占库存异常：" + e.getMessage());
        }
    }

    /**
     * 释放预占
     *
     * @param callDto     输入信息
     * @param callbackDto 输出信息
     */
    public void releasePreemptionStock(StockCallDto callDto, StockCallbackDto callbackDto) {
        List<Map<String, String>> errorList = new ArrayList<>();
        //成功信息集合
        List<StockResultDto> resultDtoList = new ArrayList<>();
        try {
            //操作库存释放
            Map<String, StockAmountDto> amountMap = operateStockRelease(callDto, errorList, resultDtoList);
            //如果没有错误信息,更新数据
            if (CollUtil.isEmpty(errorList)) {
                wmsStockAmountService.preemptionStockAmount(new ArrayList<>(amountMap.values()));
                //释放完成后删除操作明细，这里可用其他方案替代
                wmsStockOperateDetailService.deleteStockOperateDetail(callDto.getBillId());
                //成功信息
                callbackDto.setResultDtoList(resultDtoList);
                callbackDto.setResult(Boolean.TRUE);
            } else {
                //失败信息
                callbackDto.setErrorList(errorList);
            }
        } catch (Exception e) {
            callbackDto.setMsg("释放预占异常：" + e.getMessage());
        }
    }

    /**
     * 操作库存释放
     *
     * @param callDto       输入
     * @param errorList     输出错误
     * @param resultDtoList 输出结果
     * @return 更新的数据
     */
    private Map<String, StockAmountDto> operateStockRelease(StockCallDto callDto, List<Map<String, String>> errorList, List<StockResultDto> resultDtoList) {
        //3.获取对应的预占操作流水
        WmsStockOperateDetailQuery query = new WmsStockOperateDetailQuery();
        query.setBillId(callDto.getBillId());
        switch (callDto.getType()) {
            case WmsStockConstants.OPERATE_TYPE_RP:
            case WmsStockConstants.OPERATE_TYPE_PO:
                query.setType(WmsStockConstants.OPERATE_TYPE_P);
                break;
            case WmsStockConstants.OPERATE_TYPE_RF:
            case WmsStockConstants.OPERATE_TYPE_FO:
                query.setType(WmsStockConstants.OPERATE_TYPE_F);
                break;
        }

        List<WmsStockOperateDetail> operateDetails = wmsStockOperateDetailService.selectStockOperateDetail(query);

        //数据更新的map
        Map<String, StockAmountDto> amountMap = new HashMap<>();
        for (StockOutputDto stockOutputDto : callDto.getOutputDtoList()) {
            //需要返回的结果
            StockResultDto resultDto = new StockResultDto();
            //错误信息
            Map<String, String> errorMap = new HashMap<>();
            //如果操作流水为空，返回明细信息
            if (CollUtil.isEmpty(operateDetails)) {
                errorMap.put(stockOutputDto.getBillDetailId(), "没有对应的占用记录！");
                errorList.add(errorMap);
                continue;
            }
            //单据明细释放有占用记录
            Optional<WmsStockOperateDetail> optional = operateDetails.stream().filter(item -> item.getBillDetailId().equals(stockOutputDto.getBillDetailId())).findFirst();
            if (!optional.isPresent()) {
                errorMap.put(stockOutputDto.getBillDetailId(), "没有对应的占用记录！");
                errorList.add(errorMap);
                continue;
            }
            //对应的库存操作明细
            WmsStockOperateDetail operateDetail = optional.get();
            //获取需要更新的库存数据释放存在map
            StockAmountDto stockAmountDto = amountMap.get(operateDetail.getStockAmountId());
            //如果不存在就需要从数据库查询库存数量
            if (stockAmountDto == null) {
                stockAmountDto = new StockAmountDto();
                WmsStockAmount stockAmount = wmsStockAmountService.getById(operateDetail.getStockAmountId());
                //库存id
                stockAmountDto.setStockAmountId(stockAmount.getId());
                //总量
                stockAmountDto.setTotalAmount(stockAmount.getTotalAmount());
                //预占量
                stockAmountDto.setPreemptionAmount(stockAmount.getPreemptionAmount());
                //冻结量
                stockAmountDto.setFreezeAmount(stockAmount.getFreezeAmount());
                //实占量
                stockAmountDto.setOutAmount(stockAmount.getOutAmount());

                resultDto.setStockAmountId(stockAmountDto.getStockAmountId());
                resultDto.setStockWarehouseId(stockAmount.getStockWarehouseId());
                resultDto.setStockAttributeId(stockAmount.getStockAttributeId());
                resultDto.setStockDimensionId(stockAmount.getStockDimensionId());
            }
            //根据操作类型设置对应的值
            switch (callDto.getType()) {
                case WmsStockConstants.OPERATE_TYPE_RP:
                    //释放后的预占量
                    BigDecimal preemptionAmount = stockAmountDto.getPreemptionAmount().subtract(operateDetail.getAmount());
                    //释放后的预占量小于0，释放失败
                    if (preemptionAmount.compareTo(BigDecimal.ZERO) < 0) {
                        errorMap.put(stockOutputDto.getBillDetailId(), "预占量不足释放！");
                        errorList.add(errorMap);
                        continue;
                    }
                    //赋值释放后的预占量
                    stockAmountDto.setPreemptionAmount(preemptionAmount);
                    break;
                case WmsStockConstants.OPERATE_TYPE_RF:
                    //释放后的冻结量
                    BigDecimal freeAmount = stockAmountDto.getFreezeAmount().subtract(operateDetail.getAmount());
                    //释放后的冻结量小于，释放失败
                    if (freeAmount.compareTo(BigDecimal.ZERO) < 0) {
                        errorMap.put(stockOutputDto.getBillDetailId(), "冻结量不足释放！");
                        errorList.add(errorMap);
                        continue;
                    }
                    //赋值释放后的冻结量
                    stockAmountDto.setFreezeAmount(freeAmount);
                    break;
                case WmsStockConstants.OPERATE_TYPE_FO:
                    //释放后的冻结量
                    BigDecimal amount = stockAmountDto.getFreezeAmount().subtract(operateDetail.getAmount());
                    //释放后的冻结量小于，释放失败
                    if (amount.compareTo(BigDecimal.ZERO) < 0) {
                        errorMap.put(stockOutputDto.getBillDetailId(), "冻结量不足释放！");
                        errorList.add(errorMap);
                        continue;
                    }
                    //冻结量
                    stockAmountDto.setFreezeAmount(amount);
                    //实占量
                    stockAmountDto.setOutAmount(stockAmountDto.getOutAmount().add(operateDetail.getAmount()));
                    break;
                case WmsStockConstants.OPERATE_TYPE_PO:
                    //释放后的预占量
                    BigDecimal subtract = stockAmountDto.getPreemptionAmount().subtract(operateDetail.getAmount());
                    //释放后的预占量小于0，释放失败
                    if (subtract.compareTo(BigDecimal.ZERO) < 0) {
                        errorMap.put(stockOutputDto.getBillDetailId(), "预占量不足释放！");
                        errorList.add(errorMap);
                        continue;
                    }
                    //预占量
                    stockAmountDto.setPreemptionAmount(subtract);
                    //实占量
                    stockAmountDto.setOutAmount(stockAmountDto.getOutAmount().add(operateDetail.getAmount()));
                    break;
            }
            amountMap.put(operateDetail.getStockAmountId(), stockAmountDto);

            resultDto.setType(callDto.getType());
            resultDto.setBillId(stockOutputDto.getBillId());
            resultDto.setBillCode(stockOutputDto.getBillCode());
            resultDto.setBillDetailId(stockOutputDto.getBillDetailId());
            resultDto.setAmount(operateDetail.getAmount());

            resultDtoList.add(resultDto);
        }
        return amountMap;
    }

    /**
     * 释放冻结
     *
     * @param callDto     输入信息
     * @param callbackDto 输出信息
     */
    public void releaseFreezeStock(StockCallDto callDto, StockCallbackDto callbackDto) {
        List<Map<String, String>> errorList = new ArrayList<>();
        //成功信息集合
        List<StockResultDto> resultDtoList = new ArrayList<>();
        try {
            //操作库存释放
            Map<String, StockAmountDto> amountMap = operateStockRelease(callDto, errorList, resultDtoList);
            //如果没有错误信息,更新数据
            if (CollUtil.isEmpty(errorList)) {
                wmsStockAmountService.freezeStockAmount(new ArrayList<>(amountMap.values()));
                //释放完成后删除操作明细，这里可用其他方案替代
                wmsStockOperateDetailService.deleteStockOperateDetail(callDto.getBillId());
                //成功信息
                callbackDto.setResultDtoList(resultDtoList);
                callbackDto.setResult(Boolean.TRUE);
            } else {
                //失败信息
                callbackDto.setErrorList(errorList);
            }
        } catch (Exception e) {
            callbackDto.setMsg("释放冻结异常：" + e.getMessage());
        }
    }

    @Override
    public PageResult<StockDetailDto> pageStockDetail(StockDetailQuery query) {
        return this.wmsStockAmountService.pageStockDetail(query);
    }

    /**
     * 校验数据
     *
     * @param callDto   入库数据
     * @param errorList 错误信息
     */
    private String checkData(StockCallDto callDto, List<Map<String, String>> errorList) {
        StringBuffer error = new StringBuffer();
        ValidationUtils.checkProperty(SpringUtils.getBean(Validator.class), callDto, new ArrayList<>(), StockCallDto.class, error);
        if (StringUtils.isNotEmpty(error.toString())) {
            return error.toString();
        }
        if (CollUtil.isEmpty(callDto.getOutputDtoList())) {
            return "出库信息不能为空!";
        }
        switch (callDto.getType()) {
            //入库
            case WmsStockConstants.OPERATE_TYPE_I:
                checkStockInputDto(callDto.getInputDtoList(), errorList);
                break;
            case WmsStockConstants.OPERATE_TYPE_P:
            case WmsStockConstants.OPERATE_TYPE_F:
            case WmsStockConstants.OPERATE_TYPE_O:
                checkStockOutputDto(callDto.getOutputDtoList(), errorList);
                if (CollUtil.isEmpty(errorList)) {
                    //由于预占、冻结、实占只要对应的单据存在记录就不可以继续操作,这里传null
                    checkExistsOperateDetail(callDto.getOutputDtoList(), errorList, null);
                }
                break;
            case WmsStockConstants.OPERATE_TYPE_PO:
            case WmsStockConstants.OPERATE_TYPE_FO:
                checkStockOutputDto(callDto.getOutputDtoList(), errorList);
                if (CollUtil.isEmpty(errorList)) {
                    //校验是否有前置操作记录
                    checkExistsOperateDetail(callDto.getOutputDtoList(), errorList, WmsStockConstants.OPERATE_TYPE_O);
                    //校验是否有后置操作记录
                    checkExistsOperateDetail(callDto.getOutputDtoList(), errorList, callDto.getType());
                }
                break;
            case WmsStockConstants.OPERATE_TYPE_RP:
            case WmsStockConstants.OPERATE_TYPE_RF:
                checkStockOutputDto(callDto.getOutputDtoList(), errorList);
                break;
        }
        return error.toString();
    }

    /**
     * 校验入库信息
     *
     * @param inputDtoList 出库信息
     * @param errorList    错误信息
     */
    private void checkStockInputDto(List<StockInputDto> inputDtoList, List<Map<String, String>> errorList) {
        for (StockInputDto stockInputDto : inputDtoList) {
            String inDetailId = stockInputDto.getStockAttributes().getInDetailId();
            Map<String, String> errorMap = new HashMap<>();
            StringBuffer error = new StringBuffer();
            //校验数量
            ValidationUtils.checkProperty(SpringUtils.getBean(Validator.class), stockInputDto, new ArrayList<>(), StockInputDto.class, error);
            //库存维度
            ValidationUtils.checkProperty(SpringUtils.getBean(Validator.class), stockInputDto.getStockDimensions(), new ArrayList<>(), WmsStockDimensions.class, error);
            //库存仓库
            ValidationUtils.checkProperty(SpringUtils.getBean(Validator.class), stockInputDto.getStockWarehouse(), new ArrayList<>(), WmsStockWarehouse.class, error);
            //库存属性
            ValidationUtils.checkProperty(SpringUtils.getBean(Validator.class), stockInputDto.getStockAttributes(), new ArrayList<>(), WmsStockAttributes.class, error);
            if (StringUtils.isNotEmpty(error)) {
                errorMap.put(inDetailId, error.toString());
                errorList.add(errorMap);
            }
        }
    }

    /**
     * 检查是否存在操作记录
     *
     * @param outputDtoList 出库信息
     * @param errorList     错误信息
     * @param type          操作类型
     */
    private void checkExistsOperateDetail(List<StockOutputDto> outputDtoList, List<Map<String, String>> errorList, String type) {
        String msg1 = "操作类型[%s]记录已存在!";
        String msg2 = "操作记录已存在!";
        for (StockOutputDto stockOutputDto : outputDtoList) {
            String billDetailId = stockOutputDto.getBillDetailId();
            Map<String, String> errorMap = new HashMap<>();
            //校验释放有操作记录
            WmsStockOperateDetailQuery query = new WmsStockOperateDetailQuery();
            query.setBillDetailId(stockOutputDto.getBillDetailId());
            query.setType(type);
            List<WmsStockOperateDetail> stockOperateDetails = wmsStockOperateDetailService.selectStockOperateDetail(query);
            if (CollUtil.isNotEmpty(stockOperateDetails)) {
                errorMap.put(billDetailId, StringUtils.isNotEmpty(type) ? String.format(msg1, type) : msg2);
                errorList.add(errorMap);
            }
        }
    }


    /**
     * 校验出库信息
     *
     * @param outputDtoList 出库信息
     * @param errorList     错误信息
     */
    private void checkStockOutputDto(List<StockOutputDto> outputDtoList, List<Map<String, String>> errorList) {
        for (StockOutputDto stockOutputDto : outputDtoList) {
            String billDetailId = stockOutputDto.getBillDetailId();
            Map<String, String> errorMap = new HashMap<>();
            StringBuffer error = new StringBuffer();
            ValidationUtils.checkProperty(SpringUtils.getBean(Validator.class), stockOutputDto, new ArrayList<>(), StockOutputDto.class, error);
            if (StringUtils.isNotEmpty(error)) {
                errorMap.put(billDetailId, error.toString());
                errorList.add(errorMap);
            }
        }
    }


    /**
     * 处理库存属性信息
     *
     * @param stockAttributeId 库存属性id（有对应仓库信息才有）
     * @param stockWarehouse   库存仓库信息
     * @param stockAttribute   库存属性
     * @return 结果
     */
    private WmsStockAttributes dealStockAttribute(String stockAttributeId, WmsStockWarehouse stockWarehouse, WmsStockAttributes stockAttribute) {
        //入库时间
        stockAttribute.setInDate(LocalDateTime.now());
        //如果非首次入库时，需要将首次入库信息保存进去
        if (StringUtils.isNotEmpty(stockAttributeId)) {
            WmsStockAttributes attributes = wmsStockAttributesService.getById(stockAttributeId);
            //首次入库时间
            stockAttribute.setFirstInDate(attributes.getFirstInDate());
            //首次入库id
            stockAttribute.setFirstInId(attributes.getFirstInId());
            //首次入库编码
            stockAttribute.setFirstInCode(attributes.getFirstInCode());
            //首次入库明细id
            stockAttribute.setFirstInDetailId(attributes.getFirstInDetailId());
            //库存批次
            stockAttribute.setStockBatch(attributes.getStockBatch());
        } else {
            //首次入库时间为实际入库时间
            stockAttribute.setFirstInDate(stockAttribute.getFactInDate());
            //首次入库id
            stockAttribute.setFirstInId(stockAttribute.getInId());
            //首次入库编码
            stockAttribute.setFirstInCode(stockAttribute.getInCode());
            //首次入库明细id
            stockAttribute.setFirstInDetailId(stockAttribute.getInDetailId());
            //库存批次：仓库编码+时间戳
            String stockBatch = stockWarehouse.getWarehouseCode() + "-" + DateUtils.dateTimeNow();
            stockAttribute.setStockBatch(stockBatch);
        }
        //每次入库的属性都不一样这里每次都新增
        wmsStockAttributesService.save(stockAttribute);
        return stockAttribute;
    }

    /**
     * 处理库存维度信息
     *
     * @param stockDimensions 库存维度
     * @return 结果
     */
    private WmsStockDimensions dealStockDimensions(WmsStockDimensions stockDimensions) {
        //判断维度是否存在
        WmsStockDimensionsQuery query = new WmsStockDimensionsQuery();
        query.setSkuId(stockDimensions.getSkuId());
        List<WmsStockDimensions> dimensions = wmsStockDimensionsService.selectWmsStockDimensionsList(query);
        if (CollUtil.isEmpty(dimensions)) {
            wmsStockDimensionsService.save(stockDimensions);
        } else {
            stockDimensions = dimensions.get(0);
        }
        return stockDimensions;
    }

    /**
     * 处理仓库信息
     *
     * @param stockWarehouse 仓库信息
     * @return 结果
     */
    private WmsStockWarehouse dealStockWarehouse(WmsStockWarehouse stockWarehouse) {
        WmsStockWarehouseQuery query = new WmsStockWarehouseQuery();
        //仓库id
        query.setWarehouseId(stockWarehouse.getWarehouseId());
        //库区id
        query.setAreaId(stockWarehouse.getAreaId());
        //货架id
        query.setShelvesId(stockWarehouse.getShelvesId());
        //库位id
        query.setLocationId(stockWarehouse.getLocationId());
        List<WmsStockWarehouse> list = wmsStockWarehouseService.selectWmsStockWarehouseList(query);
        //仓库信息不存在，新增一条数据
        if (CollUtil.isEmpty(list)) {
            wmsStockWarehouseService.save(stockWarehouse);
        } else {
            stockWarehouse = list.get(0);
        }
        return stockWarehouse;
    }
}
